library(dplyr)
library(tidyr)
library(ggplot2)
library(rgdal)
library(raster)

Set your working directory to the folder you just downloaded or cloned:

setwd("~/Desktop/r_geospatial_102219/")
Error in setwd("~/Desktop/r_geospatial_102219/") : 
  cannot change working directory

Outline:

1. Reading and plotting raster data

First, let’s look at raster metadata using GDALinfo

GDALinfo("./data/HARV_dsmCrop.tif")
rows        1367 
columns     1697 
bands       1 
lower left origin.x        731453 
lower left origin.y        4712471 
res.x       1 
res.y       1 
ysign       -1 
oblique.x   0 
oblique.y   0 
driver      GTiff 
projection  +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs 
file        ./data/HARV_dsmCrop.tif 
apparent band summary:
   GDType hasNoDataValue NoDataValue blockSize1 blockSize2
1 Float64           TRUE       -9999          1       1697
apparent band statistics:
    Bmin   Bmax    Bmean      Bsd
1 305.07 416.07 359.8531 17.83169
Metadata:
AREA_OR_POINT=Area 

Load raster

Note: raster() will create a single-band raster. stack() and brick() can be used to load/create a multi-band raster (more on that later)

harv_dsm <- raster("./data/HARV_dsmCrop.tif")
harv_dsm
class       : RasterLayer 
dimensions  : 1367, 1697, 2319799  (nrow, ncol, ncell)
resolution  : 1, 1  (x, y)
extent      : 731453, 733150, 4712471, 4713838  (xmin, xmax, ymin, ymax)
coord. ref. : +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0 
data source : /Users/francesdavenport/Documents/teaching/r_geospatial/r-geospatial-102219/data/HARV_dsmCrop.tif 
names       : HARV_dsmCrop 
values      : 305.07, 416.07  (min, max)

We can plot a raster using base R plotting…

plot(harv_dsm)

…or using ggplot (after creating a data.frame with the data from our raster)

harv_dsm_df <- as.data.frame(harv_dsm, xy = TRUE)
str(harv_dsm_df)
'data.frame':   2319799 obs. of  3 variables:
 $ x           : num  731454 731454 731456 731456 731458 ...
 $ y           : num  4713838 4713838 4713838 4713838 4713838 ...
 $ HARV_dsmCrop: num  409 408 407 407 409 ...
ggplot() + 
  geom_raster(data = harv_dsm_df, aes(x, y, fill = HARV_dsmCrop)) + 
  scale_fill_viridis_c()

2. Raster calculations

Let’s calculate canopy height from a DSM (digital surface model) and a DTM (digital terrain model)

Let’s read in our digital terrain model

harv_dtm <- raster("./data/HARV_dtmCrop.tif")
plot(harv_dtm)

Raster math: we can perform calculations between two rasters

Let’s calculate canopy height by subtracting the DTM from the DSM

harv_canopy <- harv_dsm - harv_dtm
plot(harv_canopy)

Raster math can be slow for large rasters or complex calculations…an alternative is to use overlay

harv_canopy <- overlay(harv_dsm, harv_dtm, 
                       fun = function(r1, r2) { return( r1 - r2) })
plot(harv_canopy)

We can also do calculations between rasters and integers

Let’s calculate the mean height in our DTM and subtract that from our DTM

harv_dtm2 <- harv_dtm - cellStats(harv_dtm, stat='mean')
plot(harv_dtm2)

Raster calc with logical expressions

harv_dtm3 <- calc(harv_dtm, function(x) ifelse(x > 340, x, NA))
plot(harv_dtm3)

Saving our canopy height model for future use:

Let’s write a GeoTiff file. R will recognize the desired output file type based on the file extension (see documentation for options)

writeRaster(harv_canopy, "./data/HARV_canopy_height.tif")

Exercise: come up with your own raster calculation and see if it works! Discuss with your neighbor

3. Reading and plotting vector data

Let’s read in a shapefile with the boundary of our field site:

Using the sp package:

aoi_sp <- shapefile("./data/HarClip_UTMZ18.shp")
aoi_sp
class       : SpatialPolygonsDataFrame 
features    : 1 
extent      : 732128, 732251.1, 4713209, 4713359  (xmin, xmax, ymin, ymax)
coord. ref. : +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0 
variables   : 1
names       : id 
value       :  1 
plot(aoi_sp)

Now let’s read in the same shapefile using the sf (“simple features”) package:

library(sf)
Linking to GEOS 3.7.2, GDAL 2.4.2, PROJ 5.2.0
aoi <- st_read("./data/HarClip_UTMZ18.shp")
Reading layer `HarClip_UTMZ18' from data source `/Users/francesdavenport/Documents/teaching/r_geospatial/r-geospatial-102219/data/HarClip_UTMZ18.shp' using driver `ESRI Shapefile'
Simple feature collection with 1 feature and 1 field
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 732128 ymin: 4713209 xmax: 732251.1 ymax: 4713359
epsg (SRID):    32618
proj4string:    +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs
plot(aoi)

Let’s try another shapefile with multiple objects:

ne_states <- st_read("./data/Boundary-US-State-NEast.shp")
Reading layer `Boundary-US-State-NEast' from data source `/Users/francesdavenport/Documents/teaching/r_geospatial/r-geospatial-102219/data/Boundary-US-State-NEast.shp' using driver `ESRI Shapefile'
Simple feature collection with 12 features and 9 fields
geometry type:  MULTIPOLYGON
dimension:      XYZ
bbox:           xmin: -80.51989 ymin: 37.91685 xmax: -66.9499 ymax: 47.45716
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
head(ne_states)
Simple feature collection with 6 features and 9 fields
geometry type:  MULTIPOLYGON
dimension:      XYZ
bbox:           xmin: -79.76212 ymin: 37.91685 xmax: -66.9499 ymax: 47.45716
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
  STATEFP  STATENS    AFFGEOID GEOID STUSPS                 NAME LSAD        ALAND      AWATER
1      11 01702382 0400000US11    11     DC District of Columbia   00    158350578    18633500
2      24 01714934 0400000US24    24     MD             Maryland   00  25147575220  6983455225
3      36 01779796 0400000US36    36     NY             New York   00 122054577774 19242052501
4      09 01779780 0400000US09    09     CT          Connecticut   00  12542396439  1814978794
5      23 01779787 0400000US23    23     ME                Maine   00  79885244456 11749091526
6      25 00606926 0400000US25    25     MA        Massachusetts   00  20203936287  7131805598
                        geometry
1 MULTIPOLYGON Z (((-77.11976...
2 MULTIPOLYGON Z (((-76.04621...
3 MULTIPOLYGON Z (((-72.01893...
4 MULTIPOLYGON Z (((-73.69594...
5 MULTIPOLYGON Z (((-68.92401...
6 MULTIPOLYGON Z (((-70.27553...
plot(ne_states)

names(ne_states)
 [1] "STATEFP"  "STATENS"  "AFFGEOID" "GEOID"    "STUSPS"   "NAME"     "LSAD"     "ALAND"    "AWATER"   "geometry"

We can plot SF objects with ggplot!

ggplot() + 
  geom_sf(data = ne_states, aes(fill = NAME))

Some common sf-specific commands:

st_bbox(ne_states)
     xmin      ymin      xmax      ymax 
-80.51989  37.91685 -66.94989  47.45716 
st_crs(ne_states)
Coordinate Reference System:
  EPSG: 4326 
  proj4string: "+proj=longlat +datum=WGS84 +no_defs"

Simple features objects also work with many dplyr/tidyr type commands:

nrow(ne_states)
[1] 12
mass <- ne_states %>%
  filter(NAME == "Massachusetts")
ggplot() + 
  geom_sf(data = mass, aes(fill = NAME))

In addition to polygon shapefiles, we can read point and line data:

tower <- st_read("./data/HARVtower_UTM18N.shp")
Reading layer `HARVtower_UTM18N' from data source `/Users/francesdavenport/Documents/teaching/r_geospatial/r-geospatial-102219/data/HARVtower_UTM18N.shp' using driver `ESRI Shapefile'
Simple feature collection with 1 feature and 14 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: 732183.2 ymin: 4713265 xmax: 732183.2 ymax: 4713265
epsg (SRID):    32618
proj4string:    +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs
roads <- st_read("./data/HARV_roads.shp")
Reading layer `HARV_roads' from data source `/Users/francesdavenport/Documents/teaching/r_geospatial/r-geospatial-102219/data/HARV_roads.shp' using driver `ESRI Shapefile'
Simple feature collection with 13 features and 15 fields
geometry type:  MULTILINESTRING
dimension:      XY
bbox:           xmin: 730741.2 ymin: 4711942 xmax: 733295.5 ymax: 4714260
epsg (SRID):    32618
proj4string:    +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs
site_map <- ggplot(roads) + 
  geom_sf() + 
  geom_sf(data = tower, col = "red", size = 10, shape = "*")

Note: ggplot is displaying coordinates as lat/lon, even though our vector data has the UTM projection!

st_crs(tower)
Coordinate Reference System:
  EPSG: 32618 
  proj4string: "+proj=utm +zone=18 +datum=WGS84 +units=m +no_defs"

4. Combining vector and raster data

Example 1: Let’s calculate a 200m buffer around our flux tower

Create 200m buffer around flux tower

tower_buffer <- st_buffer(tower, 200)
site_map + 
  geom_sf(data = tower_buffer, fill = "green", alpha = 0.2) 

“Clip” canopy height model to 200m around flux tower

Note: We could use crop to clip the raster to the bounding box of an object, but not it’s actual boundary

canopy_masked <- mask(harv_canopy, tower_buffer)
canopy_masked
class       : RasterLayer 
dimensions  : 1367, 1697, 2319799  (nrow, ncol, ncell)
resolution  : 1, 1  (x, y)
extent      : 731453, 733150, 4712471, 4713838  (xmin, xmax, ymin, ymax)
coord. ref. : +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0 
data source : in memory
names       : layer 
values      : 0, 38.16998  (min, max)
plot(canopy_masked)

Instead, maybe we just want the canopy height values for all cells within our buffer

buffer_canopy_values <- extract(harv_canopy, tower_buffer)

Extract returns a list of vectors (one for each polygon object), but we only have one object so we can unlist the result

buffer_canopy_values <- unlist(buffer_canopy_values)
hist(buffer_canopy_values)

Example 2: extracting time series data from multiple rasters

Let’s find our NDVI (normalized difference vegetation index) files and read them in

ndvi_files <- list.files("./data/NDVI/", 
                            full.names = TRUE,
                            pattern = ".tif$")
harv_ndvi <- stack(ndvi_files)
harv_ndvi
class       : RasterStack 
dimensions  : 5, 4, 20, 13  (nrow, ncol, ncell, nlayers)
resolution  : 30, 30  (x, y)
extent      : 239415, 239535, 4714215, 4714365  (xmin, xmax, ymin, ymax)
coord. ref. : +proj=utm +zone=19 +ellps=WGS84 +units=m +no_defs 
names       : X005_HARV_ndvi_crop, X037_HARV_ndvi_crop, X085_HARV_ndvi_crop, X133_HARV_ndvi_crop, X181_HARV_ndvi_crop, X197_HARV_ndvi_crop, X213_HARV_ndvi_crop, X229_HARV_ndvi_crop, X245_HARV_ndvi_crop, X261_HARV_ndvi_crop, X277_HARV_ndvi_crop, X293_HARV_ndvi_crop, X309_HARV_ndvi_crop 
min values  :                2732,                1534,                1917,                5669,                8685,                8768,                8633,                8686,                8297,                7491,                 277,                 484,                4396 
max values  :                5545,                3736,                3600,                6394,                8903,                9140,                8945,                8967,                8651,                8607,                 366,                 661,                6359 
crs(harv_ndvi)
CRS arguments:
 +proj=utm +zone=19 +ellps=WGS84 +units=m +no_defs 

Wait, this is a different zone! Let’s reproject the raster to UTM zone 18

crs(harv_dtm)
CRS arguments:
 +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0 
harv_ndvi <- projectRaster(harv_ndvi, crs = crs(harv_dtm))
no non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Inf

Now, let’s calculate mean NDVI on each date

ndvi_mean <- cellStats(harv_ndvi, mean)
plot(ndvi_mean)

Instead, let’s calculate mean NDVI at our tower location

We could do this by finding the cell overlapping with the tower and extracting the value at that point

tower_xy <- st_coordinates(tower)
tower_row <- rowFromY(harv_ndvi, tower_xy[2])
tower_col <- colFromX(harv_ndvi, tower_xy[1])
tower_ndvi <- getValues(harv_ndvi, row = tower_row)[tower_col,]
plot(tower_ndvi)

We can do the same thing using extract (with fewer lines of code!)

tower_ndvi <- raster::extract(harv_ndvi, tower)
buffer_ndvi <- raster::extract(harv_ndvi, tower_buffer, fun = mean)
buffer_ndvi
     X005_HARV_ndvi_crop X037_HARV_ndvi_crop X085_HARV_ndvi_crop X133_HARV_ndvi_crop X181_HARV_ndvi_crop
[1,]            3709.251            2507.582            2571.568            6004.585             8780.76
     X197_HARV_ndvi_crop X213_HARV_ndvi_crop X229_HARV_ndvi_crop X245_HARV_ndvi_crop X261_HARV_ndvi_crop
[1,]            8941.279            8783.959            8818.749            8503.574            7955.772
     X277_HARV_ndvi_crop X293_HARV_ndvi_crop X309_HARV_ndvi_crop
[1,]            332.0345            561.2631            5457.627

Optional: Let’s make this data look a little bit nicer and plot it:

ndvi_ts <- as.data.frame(unlist(buffer_ndvi)[1,])
names(ndvi_ts) <- "meanNDVI"
julian_day <- gsub("X|_HARV_ndvi_crop", "", row.names(ndvi_ts))
origin <- as.Date("2011-01-01")
ndvi_ts$date <- origin + as.integer(julian_day) - 1
ndvi_ts
                     meanNDVI       date
X005_HARV_ndvi_crop 3709.2514 2011-01-05
X037_HARV_ndvi_crop 2507.5816 2011-02-06
X085_HARV_ndvi_crop 2571.5682 2011-03-26
X133_HARV_ndvi_crop 6004.5853 2011-05-13
X181_HARV_ndvi_crop 8780.7595 2011-06-30
X197_HARV_ndvi_crop 8941.2789 2011-07-16
X213_HARV_ndvi_crop 8783.9587 2011-08-01
X229_HARV_ndvi_crop 8818.7488 2011-08-17
X245_HARV_ndvi_crop 8503.5736 2011-09-02
X261_HARV_ndvi_crop 7955.7723 2011-09-18
X277_HARV_ndvi_crop  332.0345 2011-10-04
X293_HARV_ndvi_crop  561.2631 2011-10-20
X309_HARV_ndvi_crop 5457.6267 2011-11-05
ggplot(ndvi_ts, aes(date, meanNDVI)) + 
  geom_point() + 
  geom_line()

Exercise: Come up with your own analysis using the raster and shapefile data that we have

Remember, our vector data includes roads and our area of interest, in addition to making our own areas using things like st_buffer! Try to plot your results and discuss it with your neighbor

LS0tCnRpdGxlOiAiUiBHZW9zcGF0aWFsIE92ZXJ2aWV3IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciwgd2FybmluZz1GLCByZXN1bHRzPSJoaWRlIn0KbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KHJhc3RlcikKYGBgCiMjIyBTZXQgeW91ciB3b3JraW5nIGRpcmVjdG9yeSB0byB0aGUgZm9sZGVyIHlvdSBqdXN0IGRvd25sb2FkZWQgb3IgY2xvbmVkOiAKYGBge3J9CnNldHdkKCJ+L0Rlc2t0b3Avcl9nZW9zcGF0aWFsXzEwMjIxOS8iKQpgYGAKIyBPdXRsaW5lOiAKKiBSZWFkaW5nIGFuZCBwbG90dGluZyByYXN0ZXIgZGF0YQoqIFJhc3RlciBjYWxjdWxhdGlvbnMKKiBSZWFkaW5nIGFuZCBwbG90dGluZyB2ZWN0b3IgZGF0YQoqIENvbWJpbmVkIHZlY3RvciBhbmQgcmFzdGVyIG1hbmlwdWxhdGlvbgogICAgKiBFeGFtcGxlIDE6IGNhbGN1bGF0aW5nIGEgYnVmZmVyIGFyb3VuZCB0aGUgZmx1eCB0b3dlcgogICAgKiBFeGFtcGxlIDI6IGV4dHJhY3RpbmcgdGltZS1zZXJpZXMgZGF0YSBmcm9tIGEgbXVsdGktYmFuZCByYXN0ZXIKCiMgMS4gUmVhZGluZyBhbmQgcGxvdHRpbmcgcmFzdGVyIGRhdGEKIyMjIEZpcnN0LCBsZXQncyBsb29rIGF0IHJhc3RlciBtZXRhZGF0YSB1c2luZyBHREFMaW5mbwpgYGB7cn0KR0RBTGluZm8oIi4vZGF0YS9IQVJWX2RzbUNyb3AudGlmIikKYGBgCiMjIyBMb2FkIHJhc3RlcgpOb3RlOiByYXN0ZXIoKSB3aWxsIGNyZWF0ZSBhIHNpbmdsZS1iYW5kIHJhc3Rlci4gc3RhY2soKSBhbmQgYnJpY2soKSBjYW4gYmUgdXNlZCB0byBsb2FkL2NyZWF0ZSBhIG11bHRpLWJhbmQgcmFzdGVyIChtb3JlIG9uIHRoYXQgbGF0ZXIpCmBgYHtyfQpoYXJ2X2RzbSA8LSByYXN0ZXIoIi4vZGF0YS9IQVJWX2RzbUNyb3AudGlmIikKaGFydl9kc20KYGBgCiMjIyBXZSBjYW4gcGxvdCBhIHJhc3RlciB1c2luZyBiYXNlIFIgcGxvdHRpbmcuLi4KYGBge3J9CnBsb3QoaGFydl9kc20pCmBgYAojIyMgLi4ub3IgdXNpbmcgZ2dwbG90IChhZnRlciBjcmVhdGluZyBhIGRhdGEuZnJhbWUgd2l0aCB0aGUgZGF0YSBmcm9tIG91ciByYXN0ZXIpCmBgYHtyfQpoYXJ2X2RzbV9kZiA8LSBhcy5kYXRhLmZyYW1lKGhhcnZfZHNtLCB4eSA9IFRSVUUpCnN0cihoYXJ2X2RzbV9kZikKYGBgCmBgYHtyfQpnZ3Bsb3QoKSArIAogIGdlb21fcmFzdGVyKGRhdGEgPSBoYXJ2X2RzbV9kZiwgYWVzKHgsIHksIGZpbGwgPSBIQVJWX2RzbUNyb3ApKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKYGBgCgojIDIuIFJhc3RlciBjYWxjdWxhdGlvbnMKIyMjIExldCdzIGNhbGN1bGF0ZSBjYW5vcHkgaGVpZ2h0IGZyb20gYSBEU00gKGRpZ2l0YWwgc3VyZmFjZSBtb2RlbCkgYW5kIGEgRFRNIChkaWdpdGFsIHRlcnJhaW4gbW9kZWwpIApgYGB7ciBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAnNjAlJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImNhbm9weV9oZWlnaHQucG5nIikKYGBgCiMjIyBMZXQncyByZWFkIGluIG91ciBkaWdpdGFsIHRlcnJhaW4gbW9kZWwKYGBge3J9CmhhcnZfZHRtIDwtIHJhc3RlcigiLi9kYXRhL0hBUlZfZHRtQ3JvcC50aWYiKQpwbG90KGhhcnZfZHRtKQpgYGAKIyMjIFJhc3RlciBtYXRoOiB3ZSBjYW4gcGVyZm9ybSBjYWxjdWxhdGlvbnMgYmV0d2VlbiB0d28gcmFzdGVycwpMZXQncyBjYWxjdWxhdGUgY2Fub3B5IGhlaWdodCBieSBzdWJ0cmFjdGluZyB0aGUgRFRNIGZyb20gdGhlIERTTQpgYGB7cn0KaGFydl9jYW5vcHkgPC0gaGFydl9kc20gLSBoYXJ2X2R0bQpwbG90KGhhcnZfY2Fub3B5KQpgYGAKIyMjIFJhc3RlciBtYXRoIGNhbiBiZSBzbG93IGZvciBsYXJnZSByYXN0ZXJzIG9yIGNvbXBsZXggY2FsY3VsYXRpb25zLi4uYW4gYWx0ZXJuYXRpdmUgaXMgdG8gdXNlIG92ZXJsYXkKYGBge3J9CmhhcnZfY2Fub3B5IDwtIG92ZXJsYXkoaGFydl9kc20sIGhhcnZfZHRtLCAKICAgICAgICAgICAgICAgICAgICAgICBmdW4gPSBmdW5jdGlvbihyMSwgcjIpIHsgcmV0dXJuKCByMSAtIHIyKSB9KQpwbG90KGhhcnZfY2Fub3B5KQpgYGAKIyMjIFdlIGNhbiBhbHNvIGRvIGNhbGN1bGF0aW9ucyBiZXR3ZWVuIHJhc3RlcnMgYW5kIGludGVnZXJzCkxldCdzIGNhbGN1bGF0ZSB0aGUgbWVhbiBoZWlnaHQgaW4gb3VyIERUTSBhbmQgc3VidHJhY3QgdGhhdCBmcm9tIG91ciBEVE0KYGBge3J9CmhhcnZfZHRtMiA8LSBoYXJ2X2R0bSAtIGNlbGxTdGF0cyhoYXJ2X2R0bSwgc3RhdD0nbWVhbicpCnBsb3QoaGFydl9kdG0yKQpgYGAKIyMjIFJhc3RlciBjYWxjIHdpdGggbG9naWNhbCBleHByZXNzaW9ucwpgYGB7cn0KaGFydl9kdG0zIDwtIGNhbGMoaGFydl9kdG0sIGZ1bmN0aW9uKHgpIGlmZWxzZSh4ID4gMzQwLCB4LCBOQSkpCnBsb3QoaGFydl9kdG0zKQpgYGAKIyMjIFNhdmluZyBvdXIgY2Fub3B5IGhlaWdodCBtb2RlbCBmb3IgZnV0dXJlIHVzZTogCkxldCdzIHdyaXRlIGEgR2VvVGlmZiBmaWxlLiBSIHdpbGwgcmVjb2duaXplIHRoZSBkZXNpcmVkIG91dHB1dCBmaWxlIHR5cGUgYmFzZWQgb24gdGhlIGZpbGUgZXh0ZW5zaW9uIChzZWUgZG9jdW1lbnRhdGlvbiBmb3Igb3B0aW9ucykKYGBge3J9CndyaXRlUmFzdGVyKGhhcnZfY2Fub3B5LCAiLi9kYXRhL0hBUlZfY2Fub3B5X2hlaWdodC50aWYiKQpgYGAKCiMjIEV4ZXJjaXNlOiBjb21lIHVwIHdpdGggeW91ciBvd24gcmFzdGVyIGNhbGN1bGF0aW9uIGFuZCBzZWUgaWYgaXQgd29ya3MhIERpc2N1c3Mgd2l0aCB5b3VyIG5laWdoYm9yCgojIDMuIFJlYWRpbmcgYW5kIHBsb3R0aW5nIHZlY3RvciBkYXRhCiMjIyBMZXQncyByZWFkIGluIGEgc2hhcGVmaWxlIHdpdGggdGhlIGJvdW5kYXJ5IG9mIG91ciBmaWVsZCBzaXRlOiAKVXNpbmcgdGhlIHNwIHBhY2thZ2U6IApgYGB7cn0KYW9pX3NwIDwtIHNoYXBlZmlsZSgiLi9kYXRhL0hhckNsaXBfVVRNWjE4LnNocCIpCmFvaV9zcApwbG90KGFvaV9zcCkKYGBgCiMjIyBOb3cgbGV0J3MgcmVhZCBpbiB0aGUgc2FtZSBzaGFwZWZpbGUgdXNpbmcgdGhlIHNmICgic2ltcGxlIGZlYXR1cmVzIikgcGFja2FnZTogCmBgYHtyfQpsaWJyYXJ5KHNmKQphb2kgPC0gc3RfcmVhZCgiLi9kYXRhL0hhckNsaXBfVVRNWjE4LnNocCIpCmBgYAoKYGBge3J9CnBsb3QoYW9pKQpgYGAKIyMjIExldCdzIHRyeSBhbm90aGVyIHNoYXBlZmlsZSB3aXRoIG11bHRpcGxlIG9iamVjdHM6IApgYGB7cn0KbmVfc3RhdGVzIDwtIHN0X3JlYWQoIi4vZGF0YS9Cb3VuZGFyeS1VUy1TdGF0ZS1ORWFzdC5zaHAiKQpoZWFkKG5lX3N0YXRlcykKcGxvdChuZV9zdGF0ZXMpCm5hbWVzKG5lX3N0YXRlcykKYGBgCiMjIyBXZSBjYW4gcGxvdCBTRiBvYmplY3RzIHdpdGggZ2dwbG90ISAKYGBge3J9CmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gbmVfc3RhdGVzLCBhZXMoZmlsbCA9IE5BTUUpKQpgYGAKIyMjIFNvbWUgY29tbW9uIHNmLXNwZWNpZmljIGNvbW1hbmRzOiAKYGBge3J9CnN0X2Jib3gobmVfc3RhdGVzKQpzdF9jcnMobmVfc3RhdGVzKQpgYGAKIyMjIFNpbXBsZSBmZWF0dXJlcyBvYmplY3RzIGFsc28gd29yayB3aXRoIG1hbnkgZHBseXIvdGlkeXIgdHlwZSBjb21tYW5kczoKYGBge3J9Cm5yb3cobmVfc3RhdGVzKQptYXNzIDwtIG5lX3N0YXRlcyAlPiUKICBmaWx0ZXIoTkFNRSA9PSAiTWFzc2FjaHVzZXR0cyIpCgpnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IG1hc3MsIGFlcyhmaWxsID0gTkFNRSkpCmBgYApJbiBhZGRpdGlvbiB0byBwb2x5Z29uIHNoYXBlZmlsZXMsIHdlIGNhbiByZWFkIHBvaW50IGFuZCBsaW5lIGRhdGE6IApgYGB7cn0KdG93ZXIgPC0gc3RfcmVhZCgiLi9kYXRhL0hBUlZ0b3dlcl9VVE0xOE4uc2hwIikKcm9hZHMgPC0gc3RfcmVhZCgiLi9kYXRhL0hBUlZfcm9hZHMuc2hwIikKc2l0ZV9tYXAgPC0gZ2dwbG90KHJvYWRzKSArIAogIGdlb21fc2YoKSArIAogIGdlb21fc2YoZGF0YSA9IHRvd2VyLCBjb2wgPSAicmVkIiwgc2l6ZSA9IDEwLCBzaGFwZSA9ICIqIikKYGBgCk5vdGU6IGdncGxvdCBpcyBkaXNwbGF5aW5nIGNvb3JkaW5hdGVzIGFzIGxhdC9sb24sIGV2ZW4gdGhvdWdoIG91ciB2ZWN0b3IgZGF0YSBoYXMgdGhlIFVUTSBwcm9qZWN0aW9uISAKYGBge3J9CnN0X2Nycyh0b3dlcikKYGBgCgojIDQuIENvbWJpbmluZyB2ZWN0b3IgYW5kIHJhc3RlciBkYXRhCiMgRXhhbXBsZSAxOiBMZXQncyBjYWxjdWxhdGUgYSAyMDBtIGJ1ZmZlciBhcm91bmQgb3VyIGZsdXggdG93ZXIgCiMjIyBDcmVhdGUgMjAwbSBidWZmZXIgYXJvdW5kIGZsdXggdG93ZXIKYGBge3J9CnRvd2VyX2J1ZmZlciA8LSBzdF9idWZmZXIodG93ZXIsIDIwMCkKc2l0ZV9tYXAgKyAKICBnZW9tX3NmKGRhdGEgPSB0b3dlcl9idWZmZXIsIGZpbGwgPSAiZ3JlZW4iLCBhbHBoYSA9IDAuMikgCmBgYAojIyMgIkNsaXAiIGNhbm9weSBoZWlnaHQgbW9kZWwgdG8gMjAwbSBhcm91bmQgZmx1eCB0b3dlcgpOb3RlOiBXZSBjb3VsZCB1c2UgY3JvcCB0byBjbGlwIHRoZSByYXN0ZXIgdG8gdGhlIGJvdW5kaW5nIGJveCBvZiBhbiBvYmplY3QsIGJ1dCBub3QgaXQncyBhY3R1YWwgYm91bmRhcnkKYGBge3J9CmNhbm9weV9tYXNrZWQgPC0gbWFzayhoYXJ2X2Nhbm9weSwgdG93ZXJfYnVmZmVyKQpjYW5vcHlfbWFza2VkCnBsb3QoY2Fub3B5X21hc2tlZCkKYGBgCiMjIyBJbnN0ZWFkLCBtYXliZSB3ZSBqdXN0IHdhbnQgdGhlIGNhbm9weSBoZWlnaHQgdmFsdWVzIGZvciBhbGwgY2VsbHMgd2l0aGluIG91ciBidWZmZXIKYGBge3J9CmJ1ZmZlcl9jYW5vcHlfdmFsdWVzIDwtIGV4dHJhY3QoaGFydl9jYW5vcHksIHRvd2VyX2J1ZmZlcikKYGBgCkV4dHJhY3QgcmV0dXJucyBhIGxpc3Qgb2YgdmVjdG9ycyAob25lIGZvciBlYWNoIHBvbHlnb24gb2JqZWN0KSwgYnV0IHdlIG9ubHkgaGF2ZSBvbmUgb2JqZWN0IHNvIHdlIGNhbiB1bmxpc3QgdGhlIHJlc3VsdApgYGB7cn0KYnVmZmVyX2Nhbm9weV92YWx1ZXMgPC0gdW5saXN0KGJ1ZmZlcl9jYW5vcHlfdmFsdWVzKQpoaXN0KGJ1ZmZlcl9jYW5vcHlfdmFsdWVzKQpgYGAKIyBFeGFtcGxlIDI6IGV4dHJhY3RpbmcgdGltZSBzZXJpZXMgZGF0YSBmcm9tIG11bHRpcGxlIHJhc3RlcnMgCiMjIyBMZXQncyBmaW5kIG91ciBORFZJIChub3JtYWxpemVkIGRpZmZlcmVuY2UgdmVnZXRhdGlvbiBpbmRleCkgZmlsZXMgYW5kIHJlYWQgdGhlbSBpbgpgYGB7cn0KbmR2aV9maWxlcyA8LSBsaXN0LmZpbGVzKCIuL2RhdGEvTkRWSS8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGwubmFtZXMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICIudGlmJCIpCmhhcnZfbmR2aSA8LSBzdGFjayhuZHZpX2ZpbGVzKQpoYXJ2X25kdmkKYGBgCmBgYHtyfQpjcnMoaGFydl9uZHZpKQpgYGAKIyMjIFdhaXQsIHRoaXMgaXMgYSBkaWZmZXJlbnQgem9uZSEgTGV0J3MgcmVwcm9qZWN0IHRoZSByYXN0ZXIgdG8gVVRNIHpvbmUgMTgKYGBge3J9CmNycyhoYXJ2X2R0bSkKaGFydl9uZHZpIDwtIHByb2plY3RSYXN0ZXIoaGFydl9uZHZpLCBjcnMgPSBjcnMoaGFydl9kdG0pKQpgYGAKIyMjIE5vdywgbGV0J3MgY2FsY3VsYXRlIG1lYW4gTkRWSSBvbiBlYWNoIGRhdGUKYGBge3J9Cm5kdmlfbWVhbiA8LSBjZWxsU3RhdHMoaGFydl9uZHZpLCBtZWFuKQpwbG90KG5kdmlfbWVhbikKYGBgCiMjIyBJbnN0ZWFkLCBsZXQncyBjYWxjdWxhdGUgbWVhbiBORFZJIGF0IG91ciB0b3dlciBsb2NhdGlvbgpXZSBjb3VsZCBkbyB0aGlzIGJ5IGZpbmRpbmcgdGhlIGNlbGwgb3ZlcmxhcHBpbmcgd2l0aCB0aGUgdG93ZXIgYW5kIGV4dHJhY3RpbmcgdGhlIHZhbHVlIGF0IHRoYXQgcG9pbnQKYGBge3J9CnRvd2VyX3h5IDwtIHN0X2Nvb3JkaW5hdGVzKHRvd2VyKQp0b3dlcl9yb3cgPC0gcm93RnJvbVkoaGFydl9uZHZpLCB0b3dlcl94eVsyXSkKdG93ZXJfY29sIDwtIGNvbEZyb21YKGhhcnZfbmR2aSwgdG93ZXJfeHlbMV0pCgp0b3dlcl9uZHZpIDwtIGdldFZhbHVlcyhoYXJ2X25kdmksIHJvdyA9IHRvd2VyX3JvdylbdG93ZXJfY29sLF0KcGxvdCh0b3dlcl9uZHZpKQpgYGAKIyMjIFdlIGNhbiBkbyB0aGUgc2FtZSB0aGluZyB1c2luZyBleHRyYWN0ICh3aXRoIGZld2VyIGxpbmVzIG9mIGNvZGUhKQpgYGB7cn0KdG93ZXJfbmR2aSA8LSByYXN0ZXI6OmV4dHJhY3QoaGFydl9uZHZpLCB0b3dlcikKCmJ1ZmZlcl9uZHZpIDwtIHJhc3Rlcjo6ZXh0cmFjdChoYXJ2X25kdmksIHRvd2VyX2J1ZmZlciwgZnVuID0gbWVhbikKYnVmZmVyX25kdmkKYGBgCiMjIyBPcHRpb25hbDogTGV0J3MgbWFrZSB0aGlzIGRhdGEgbG9vayBhIGxpdHRsZSBiaXQgbmljZXIgYW5kIHBsb3QgaXQ6IApgYGB7cn0KbmR2aV90cyA8LSBhcy5kYXRhLmZyYW1lKHVubGlzdChidWZmZXJfbmR2aSlbMSxdKQpuYW1lcyhuZHZpX3RzKSA8LSAibWVhbk5EVkkiCmp1bGlhbl9kYXkgPC0gZ3N1YigiWHxfSEFSVl9uZHZpX2Nyb3AiLCAiIiwgcm93Lm5hbWVzKG5kdmlfdHMpKQpvcmlnaW4gPC0gYXMuRGF0ZSgiMjAxMS0wMS0wMSIpCm5kdmlfdHMkZGF0ZSA8LSBvcmlnaW4gKyBhcy5pbnRlZ2VyKGp1bGlhbl9kYXkpIC0gMQpuZHZpX3RzCmdncGxvdChuZHZpX3RzLCBhZXMoZGF0ZSwgbWVhbk5EVkkpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGdlb21fbGluZSgpCmBgYAojIyMgRXhlcmNpc2U6IENvbWUgdXAgd2l0aCB5b3VyIG93biBhbmFseXNpcyB1c2luZyB0aGUgcmFzdGVyIGFuZCBzaGFwZWZpbGUgZGF0YSB0aGF0IHdlIGhhdmUKUmVtZW1iZXIsIG91ciB2ZWN0b3IgZGF0YSBpbmNsdWRlcyByb2FkcyBhbmQgb3VyIGFyZWEgb2YgaW50ZXJlc3QsIGluIGFkZGl0aW9uIHRvIG1ha2luZyBvdXIgb3duIGFyZWFzIHVzaW5nIHRoaW5ncyBsaWtlIHN0X2J1ZmZlciEKVHJ5IHRvIHBsb3QgeW91ciByZXN1bHRzIGFuZCBkaXNjdXNzIGl0IHdpdGggeW91ciBuZWlnaGJvcgo=